package com.dyna.chat.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.dyna.chat.domain.dto.WSAuthorize;
import com.dyna.chat.domain.dto.WSChannelExtraDto;
import com.dyna.chat.domain.resp.ws.WSBaseResp;
import com.dyna.chat.service.WSAdapter;
import com.dyna.chat.service.WebSocketService;
import com.dyna.core.config.ThreadPoolConfig;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author zhoucaiwang
 * @date 2024/7/13
 */
@Slf4j
@Service
public class WebSocketServiceImpl implements WebSocketService {

    @Resource
    private WxMpService wxMpService;
    @Resource
    @Qualifier(ThreadPoolConfig.WS_EXECUTOR)
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private static final Duration EXPIRE_TIME = Duration.ofHours(1);
    private static final Long MAX_MUM_SIZE = 10000L;

    /**
     * 所有请求登录的code与channel关系
     */
    public static final Cache<Integer, Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .expireAfterWrite(EXPIRE_TIME)
            .maximumSize(MAX_MUM_SIZE)
            .build();
    /**
     * 所有已连接的websocket连接列表和一些额外参数
     */
    private static final ConcurrentHashMap<Channel, WSChannelExtraDto> ONLINE_WS_MAP = new ConcurrentHashMap<>();
    /**
     * 所有在线的用户和对应的socket
     */
    private static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> ONLINE_UID_MAP = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<Channel, WSChannelExtraDto> getOnlineMap() {
        return ONLINE_WS_MAP;
    }

    @Override
    public void connect(Channel channel) {
        ONLINE_WS_MAP.put(channel, new WSChannelExtraDto());
    }

    @Override
    public void authorize(Channel channel, WSAuthorize wsAuthorize) {
        //校验token
        //1.校验成功
        //2.校验失败，返回
        this.sendMsg(channel, WSAdapter.buildInvalidateTokenResp());
    }

    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel) {
        Random random = new Random();
        int code = random.nextInt(127);
        //生成登录二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) EXPIRE_TIME.getSeconds());
        //将二维码返回给前端
        sendMsg(channel, WSAdapter.buildLoginResp(wxMpQrCodeTicket));
    }

    /**
     * 给本地channel发送消息
     *
     * @param channel channel通道
     * @param wsBaseResp ws通用响应
     */
    private void sendMsg(Channel channel, WSBaseResp<?> wsBaseResp) {
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(wsBaseResp)));
    }

    @Override
    public void sendToUserId(WSBaseResp<?> wsBaseResp, Long userId) {
        CopyOnWriteArrayList<Channel> channels = ONLINE_UID_MAP.get(userId);
        if (CollectionUtil.isEmpty(channels)) {
            log.info("用户：{}不在线", userId);
            return;
        }
        channels.forEach(channel -> {
            threadPoolTaskExecutor.execute(() -> sendMsg(channel, wsBaseResp));
        });
    }

    @Override
    public void sendToAllOnline(WSBaseResp<?> wsBaseResp) {
        log.info("向所有在线用户发送消息...");
        sendToAllOnline(wsBaseResp, null);
    }

    @Override
    public void sendToAllOnline(WSBaseResp<?> wsBaseResp, Long skipUserId) {
        log.info("在线用户数量:::{}", ONLINE_WS_MAP.size());
        ONLINE_WS_MAP.forEach((channel, ext) -> {
            if (Objects.nonNull(skipUserId) && Objects.equals(ext.getUid(), skipUserId)) {
                return;
            }
            threadPoolTaskExecutor.execute(() -> sendMsg(channel, wsBaseResp));
        });
    }



}
